package com.smedi.ismedi.inmail.core.port.adapter.service.impl;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.smedi.ismedi.authority.starter.util.AuthorityUtil;
import com.smedi.ismedi.constructionproject.api.dto.request.ProjectAssignmentListQueryRequest;
import com.smedi.ismedi.constructionproject.api.dto.request.ProjectListQueryRequest;
import com.smedi.ismedi.constructionproject.api.dto.response.resp.ProjectAssignmentResp;
import com.smedi.ismedi.constructionproject.api.dto.response.resp.ProjectResp;
import com.smedi.ismedi.constructionproject.api.feign.RemoteConstructionProjectFeignApi;
import com.smedi.ismedi.constructionproject.api.feign.RemoteProjectAssignmentFeignApi;
import com.smedi.ismedi.inmail.core.domain.aggregatemodel.Node;
import com.smedi.ismedi.inmail.core.domain.dto.OrganizationAssignmentQuery;
import com.smedi.ismedi.inmail.core.domain.dto.PersonQuery;
import com.smedi.ismedi.inmail.core.port.adapter.convert.AdapterConverter;
import com.smedi.ismedi.inmail.core.port.adapter.service.PersonRemoteApi;
import com.smedi.ismedi.organization.api.dto.request.OrganizationListQueryRequest;
import com.smedi.ismedi.organization.api.dto.request.ProjectOrganizationListQueryRequest;
import com.smedi.ismedi.organization.api.dto.response.resp.OrganizationResp;
import com.smedi.ismedi.organization.api.dto.response.resp.ProjectOrganizationResp;
import com.smedi.ismedi.organization.api.feign.RemoteHrOrganizationFeignApi;
import com.smedi.ismedi.organization.api.feign.RemoteProjectOrganizationFeignApi;
import com.smedi.ismedi.personnel.api.dto.request.OrganizationAssignmentListQueryRequest;
import com.smedi.ismedi.personnel.api.dto.request.PersonListQueryRequest;
import com.smedi.ismedi.personnel.api.dto.response.PersonResponse;
import com.smedi.ismedi.personnel.api.dto.response.resp.OrganizationAssignmentResp;
import com.smedi.ismedi.personnel.api.dto.response.resp.PersonResp;
import com.smedi.ismedi.personnel.api.feign.RemotePersonCoreFeignApi;
import com.smedi.ismedi.web.starter.common.ResponseData;
import com.smedi.ismedi.web.starter.exception.BasicError;
import com.smedi.ismedi.web.starter.exception.LogicException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.smedi.ismedi.inmail.core.infrastructure.constant.ConstantField.PROJECT_MANAGEMENT_ORGANIZATION;
import static com.smedi.ismedi.inmail.core.infrastructure.constant.NodeTypeEnum.*;
import static com.smedi.ismedi.web.starter.thread.ThreadPool.IO_INSTANCE;

@AllArgsConstructor
@Service
public class PersonRemoteApiImpl implements PersonRemoteApi {
    private final RemotePersonCoreFeignApi remotePersonCoreFeignApi;

    private final RemoteHrOrganizationFeignApi remoteHrOrganizationFeignApi;

    private final RemoteConstructionProjectFeignApi remoteConstructionProjectFeignApi;

    private final RemoteProjectOrganizationFeignApi remoteProjectOrganizationFeignApi;

    private final RemoteProjectAssignmentFeignApi remoteProjectAssignmentFeignApi;

    private final AdapterConverter adapterConverter;

    private final AuthorityUtil authorityUtil;

    @Override
    public List<PersonResponse> getPersonInProjectWithRoleIds(Long projectId, List<Long> roleIds) {
        ResponseData<List<PersonResponse>> responseData = remotePersonCoreFeignApi.getPersonInProjectWithRoleIds(projectId, roleIds);
        if (!responseData.isSuccess()) {
            throw LogicException.raise(BasicError.CALL_REMOTE_API_ERROR, responseData.getApplication(), String.valueOf(responseData.getErrorMsg()));
        }
        return responseData.getData();
    }

    @Override
    public List<PersonResponse> getPersonInOrganizationWithRoleIds(Long organizationId, List<Long> roleIds) {
        ResponseData<List<PersonResponse>> responseData = remotePersonCoreFeignApi.getPersonInOrganizationWithRoleIds(organizationId, roleIds);
        if (!responseData.isSuccess()) {
            throw LogicException.raise(BasicError.CALL_REMOTE_API_ERROR, responseData.getApplication(), String.valueOf(responseData.getErrorMsg()));
        }
        return responseData.getData();
    }

    @Override
    public List<PersonResponse> getPersonOne() {
//        remotePersonCoreFeignApi.g
        return null;
    }

    @Override
    public List<PersonResp> getPersonList(PersonQuery personQuery) {
        PersonListQueryRequest personListQueryRequest = adapterConverter.personQueryToPersonListQueryRequest(personQuery);
        ResponseData<List<PersonResp>> responseData = remotePersonCoreFeignApi.getPersonList(personListQueryRequest);
        if (!responseData.isSuccess()) {
            throw LogicException.raise(BasicError.CALL_REMOTE_API_ERROR, responseData.getApplication(), String.valueOf(responseData.getErrorMsg()));
        }
        return responseData.getData();
    }

    @Override
    public List<OrganizationAssignmentResp> getOrganizationAssignmentList(OrganizationAssignmentQuery organizationAssignmentQuery) {
        OrganizationAssignmentListQueryRequest organizationAssignmentListQueryRequest = adapterConverter.organizationAssignmentQueryToOrganizationAssignmentListQueryRequest(organizationAssignmentQuery);
        ResponseData<List<OrganizationAssignmentResp>> responseData = remotePersonCoreFeignApi.getOrganizationAssignmentList(organizationAssignmentListQueryRequest);
        if (!responseData.isSuccess()) {
            throw LogicException.raise(BasicError.CALL_REMOTE_API_ERROR, responseData.getApplication(), String.valueOf(responseData.getErrorMsg()));
        }
        return responseData.getData();
    }

    @Override
    public Node getOrganizationTree() {
        Node root = new Node();
        Map<String, List<Node>> parentKeyNodesMap = new HashMap<>();

        AtomicReference<List<OrganizationResp>> allOrganizations = new AtomicReference<>(new ArrayList<>());
        AtomicReference<List<OrganizationAssignmentResp>> allOrganizationAssignment = new AtomicReference<>(new ArrayList<>());

        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> allOrganizations.set(remoteHrOrganizationFeignApi.getOrganizationList(OrganizationListQueryRequest.builder().build()).getData()), IO_INSTANCE),
                CompletableFuture.runAsync(() -> allOrganizationAssignment.set(remotePersonCoreFeignApi.getOrganizationAssignmentList(OrganizationAssignmentListQueryRequest.builder().build()).getData()), IO_INSTANCE)
        ).join();

        List<OrganizationResp> organizations = allOrganizations.get().stream().sorted(Comparator.comparing(OrganizationResp::getDisplayOrder)).collect(Collectors.toList());
        for (OrganizationResp organizationResp : organizations) {
            if (organizationResp.isRoot()) {
                root = Node.builder().id(organizationResp.getId()).name(organizationResp.getFullName()).type(ORGANIZATION.name()).build();
            }
            Node node = Node.builder().id(organizationResp.getId()).name(organizationResp.getFullName()).type(ORGANIZATION.name()).parent(Node.builder().id(organizationResp.getParentId()).type(ORGANIZATION.name()).build()).build();
            List<Node> nodesByParentKey = parentKeyNodesMap.getOrDefault(node.getParent().getKey(), new ArrayList<>());
            nodesByParentKey.add(node);
            parentKeyNodesMap.put(node.getParent().getKey(), nodesByParentKey);
        }

        Map<Long, Long> personIdOrganizationIdMap = new HashMap<>();
        Set<Long> personIds = new HashSet<>();
        for (OrganizationAssignmentResp organizationAssignmentResp : allOrganizationAssignment.get()) {
            personIdOrganizationIdMap.put(organizationAssignmentResp.getPersonId(), organizationAssignmentResp.getHrOrganizationId());
            personIds.add(organizationAssignmentResp.getPersonId());
        }

        List<PersonResp> people = remotePersonCoreFeignApi.getPersonList(PersonListQueryRequest.builder().ids(new ArrayList<>(personIds)).build()).getData();
        people = people.stream().sorted(Comparator.comparing(PersonResp::getPersonOrder)).collect(Collectors.toList());
        for (PersonResp person : people) {
            Node node = Node.builder().id(person.getId()).name(person.getLastName()).type(PERSON.name()).parent(Node.builder().id(personIdOrganizationIdMap.get(person.getId())).type(ORGANIZATION.name()).build()).build();
            List<Node> nodesByParentKey = parentKeyNodesMap.getOrDefault(node.getParent().getKey(), new ArrayList<>());
            nodesByParentKey.add(node);
            parentKeyNodesMap.put(node.getParent().getKey(), nodesByParentKey);
        }

        findChildren(root, parentKeyNodesMap, null);

        return root;
    }

    public Node getProjectTree() {
        Node root = new Node();
        Map<String, List<Node>> parentKeyNodesMap = new HashMap<>();

        AtomicReference<List<OrganizationResp>> allOrganizations = new AtomicReference<>(new ArrayList<>());
        AtomicReference<List<ProjectResp>> allProjects = new AtomicReference<>(new ArrayList<>());
        AtomicReference<List<ProjectOrganizationResp>> allProjectOrganizations = new AtomicReference<>(new ArrayList<>());
        AtomicReference<List<ProjectAssignmentResp>> allProjectAssignments = new AtomicReference<>(new ArrayList<>());

        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> allOrganizations.set(remoteHrOrganizationFeignApi.getOrganizationList(OrganizationListQueryRequest.builder().build()).getData()), IO_INSTANCE),
                CompletableFuture.runAsync(() -> allProjects.set(remoteConstructionProjectFeignApi.getProjectList(ProjectListQueryRequest.builder().build()).getData()), IO_INSTANCE),
                CompletableFuture.runAsync(() -> allProjectOrganizations.set(remoteProjectOrganizationFeignApi.getProjectOrganizationList(ProjectOrganizationListQueryRequest.builder().build()).getData()), IO_INSTANCE),
                CompletableFuture.runAsync(() -> allProjectAssignments.set(remoteProjectAssignmentFeignApi.getProjectAssignmentList(ProjectAssignmentListQueryRequest.builder().build()).getData()), IO_INSTANCE)
        ).join();

        Set<Long> organizationIds = new HashSet<>();
        List<ProjectResp> projects = allProjects.get().stream().sorted(Comparator.comparing(ProjectResp::getId).reversed()).collect(Collectors.toList());
        for (ProjectResp project : projects) {
            Node node = Node.builder().id(project.getId()).name(project.getProjectName()).type(PROJECT.name()).parent(Node.builder().id(project.getHrOrganizationId()).type(ORGANIZATION.toString()).build()).build();
            List<Node> nodesByParentKey = parentKeyNodesMap.getOrDefault(node.getParent().getKey(), new ArrayList<>());
            nodesByParentKey.add(node);
            parentKeyNodesMap.put(node.getParent().getKey(), nodesByParentKey);

            organizationIds.add(project.getHrOrganizationId());
        }

        Map<Long, ProjectOrganizationResp> idProjectOrganizationRespMap = new HashMap<>();
        for (ProjectOrganizationResp projectOrganization : allProjectOrganizations.get()) {
            Node node = Node.builder().id(projectOrganization.getId()).name(projectOrganization.getFullName()).type(PROJECT_ORGANIZATION.name()).parent(Node.builder().id(projectOrganization.getProjectId()).type(PROJECT.toString()).build()).build();
            List<Node> nodesByParentKey = parentKeyNodesMap.getOrDefault(node.getParent().getKey(), new ArrayList<>());
            nodesByParentKey.add(node);
            parentKeyNodesMap.put(node.getParent().getKey(), nodesByParentKey);

            idProjectOrganizationRespMap.put(projectOrganization.getId(), projectOrganization);
        }

        List<OrganizationResp> organizations = allOrganizations.get().stream().sorted(Comparator.comparing(OrganizationResp::getDisplayOrder)).collect(Collectors.toList());
        for (OrganizationResp organizationResp : organizations) {
            if (organizationResp.isRoot()) {
                root = Node.builder().id(organizationResp.getId()).name(organizationResp.getFullName()).type(ORGANIZATION.toString()).build();
            }
            if (CollectionUtils.isNotEmpty(organizationIds) && organizationIds.contains(organizationResp.getId()) && organizationResp.getType().equals(PROJECT_MANAGEMENT_ORGANIZATION)) {
                Node node = Node.builder().id(organizationResp.getId()).name(organizationResp.getFullName()).type(ORGANIZATION.toString()).parent(Node.builder().id(organizationResp.getParentId()).type(ORGANIZATION.toString()).build()).build();
                List<Node> nodesByParentKey = parentKeyNodesMap.getOrDefault(node.getParent().getKey(), new ArrayList<>());
                nodesByParentKey.add(node);
                parentKeyNodesMap.put(node.getParent().getKey(), nodesByParentKey);
            }
        }

        Set<Long> personIds = new HashSet<>();
        Map<Long, List<Long>> personIdProjectOrganizationIdsMap = new HashMap<>();
        for (ProjectAssignmentResp projectAssignmentResp : allProjectAssignments.get()) {
            List<Long> projectOrganizationIdsByPersonIds = personIdProjectOrganizationIdsMap.getOrDefault(projectAssignmentResp.getPersonId(), new ArrayList<>());
            projectOrganizationIdsByPersonIds.add(projectAssignmentResp.getProjectOrganizationId());
            personIdProjectOrganizationIdsMap.put(projectAssignmentResp.getPersonId(), projectOrganizationIdsByPersonIds);

            personIds.add(projectAssignmentResp.getPersonId());
        }

        List<PersonResp> people = remotePersonCoreFeignApi.getPersonList(PersonListQueryRequest.builder().ids(new ArrayList<>(personIds)).build()).getData();
        people = people.stream().sorted(Comparator.comparing(PersonResp::getPersonOrder)).collect(Collectors.toList());
        for (PersonResp personResp : people) {
            Long personId = personResp.getId();
            List<Long> projectOrganizationIdsByPersonId = personIdProjectOrganizationIdsMap.get(personId);
            for (Long projectOrganizationId : projectOrganizationIdsByPersonId) {
                ProjectOrganizationResp projectOrganizationResp = idProjectOrganizationRespMap.get(projectOrganizationId);
                Node node = Node.builder().id(personId).name(personResp.getLastName()).type(PERSON.toString()).parent(Node.builder().id(projectOrganizationResp.getId()).type(PROJECT_ORGANIZATION.toString()).build()).build();
                List<Node> nodesByParentKey = parentKeyNodesMap.getOrDefault(node.getParent().getKey(), new ArrayList<>());
                nodesByParentKey.add(node);
                parentKeyNodesMap.put(node.getParent().getKey(), nodesByParentKey);
            }
        }

        findChildren(root, parentKeyNodesMap, null);

        return root;
    }

    private void findChildren(Node node, Map<String, List<Node>> keyNodesMap, String prefixPath) {
        node.setPrefixPath(prefixPath);
        prefixPath = node.getPath();
        List<Node> children = keyNodesMap.getOrDefault(node.getKey(), new ArrayList<>());
        for (Node child : children) {
            findChildren(child, keyNodesMap, prefixPath);
        }
        node.setChildren(children);
    }
}
